#include "simple/support/range.hpp"
#include "simple/support/vuple.hpp"
#include "simple/support/algorithm/vmismatch.hpp"
#include "simple/support/algorithm/find.hpp"

#include <vector>
#include <list>
#include <deque>
#include <cassert>
#include <algorithm>
#include <functional>

template <typename F>
auto flatten_args(F f) { return [f](auto&& t){ return std::apply(f,t); }; }

using simple::support::vuple;
using simple::support::vbegin;
using simple::support::vend;

int main()
{
	static_assert(simple::support::vuple_depth<int>{} == 0);
	static_assert(simple::support::vuple_depth<vuple<int>>{} == 1);
	static_assert(simple::support::vuple_depth<vuple<vuple<int>>>{} == 2);
	static_assert(simple::support::vuple_depth<vuple<vuple<vuple<int>>>>{} == 3);


	// basic binary stuff
	assert(( vuple{1,2,3} == vuple{1,2,3} )); // equality works we hope
	assert(( vuple{vuple{1,2},vuple{3,4}} == vuple{vuple{1,2},vuple{3,4}} ));
	assert(( vuple{vuple{1,2},vuple{3,4}} != vuple{1,2} ));
	assert(( vuple{1,2,3} != vuple{1,2,4} )); // inequality also, not so obvious
	assert(( vuple{1,2,3} < vuple{2,3,4} )); // then comes blasphemy
	assert(( not(vuple{1,2,3} < vuple{2,0,4}) )); // oh no this is not lexicographic
	assert(( vuple{1,2,3} <= vuple{1,3,4} ));
	assert(( vuple{1,2,3} > vuple{0,1,2} ));
	assert(( vuple{1,2,3} >= vuple{0,1,3} ));
	assert(( vuple{1,2,3} + vuple{3,2,1} == vuple{4,4,4} )); // now we can check some arithmetic
	assert(( vuple{1,2,3} - vuple{3,2,1} == vuple{-2,0,2} ));
	assert(( vuple{true,false} + vuple{true,true} == vuple{2,1} )); // promotion
	assert(( vuple{1,2,3} + vuple{true,true,false} == vuple{2,3,3} ));
	assert(( 1 + vuple{1,2,3} == vuple{2,3,4} )); // mingle with element type
	assert(( vuple{1,2,3} + 2 == vuple{3,4,5} ));

	// iterators and reaches TODO: not comprehensive enough!
	const char* aaaaaa = "aaaaaaaaaaaaaaaaaaa";
	assert(( to_disjunction(aaaaaa+vuple{1,0,0} == aaaaaa+vuple{1,2,3}) ));
	assert(( reaches(aaaaaa+vuple{1,0,0},aaaaaa+vuple{1,2,3}) ));
	// assert(( reaches(vuple{aaaaaa+vuple{1,3}},vuple{aaaaaa+3}) ));
	assert(( reaches(aaaaaa+vuple{1,3},aaaaaa+3) ));
	using simple::support::reaches;
	assert(( reaches(aaaaaa,aaaaaa) ));
	// assert(( reaches(vuple{aaaaaa+vuple{1,0}, aaaaaa+vuple{0,2}, aaaaaa+vuple{0,3}},aaaaaa+vuple{1,2,3}) ));

	// yep this used to not compile yep
	assert(( vuple{1} == vuple{1} ));
	assert(( vuple{vuple{1}} == vuple{vuple{1}} ));
	static_assert( std::is_same_v<decltype(vuple{vuple{1}}), vuple<vuple<int>>> );
	static_assert( std::is_same_v<decltype(vuple{vuple{vuple{1}}}), vuple<vuple<vuple<int>>>> );
	static_assert( std::is_same_v<decltype(vuple{vuple{1,2}}), vuple<vuple<int,int>>> );

	// implicit conversion of reduction
	{
		assert(( vuple{1,2,3} - (vuple{1,2,3} > 2) == vuple{1,2,2} ));
		vuple a{1,2,3};
		a -= a <= 2;
		assert(( a == vuple{0,1,3} ));
	}

	// basic unary
	{
		assert(( -vuple{1,2,3} == vuple{-1,-2,-3} ));
		auto a = -vuple(1,2,3);
		assert(( a == vuple{-1,-2,-3} ));
		auto&& b = -vuple(1,2,3);
		assert(( b == vuple{-1,-2,-3} ));
	}

	// fmod
	{
		assert(( vuple{1.f, 2.f, 3.f} % vuple{2.f, 2.f ,2.f} == vuple{1.f, 0.f, 1.f} ));
		auto a = vuple{1.f, 2.f, 3.f};
		a %= vuple{2.f, 2.f, 2.f};
		assert(( a == vuple{1.f, 0.f, 1.f} ));
	}

	// in place left element shenanigans
	{
		auto a = 1;
		a += vuple{1,2,3}; // reduction is like narrow conversion... with += you are pretty much asking for it, it's hardly implicit
		assert(( a == 7));

		// comparison also return a reduction, so need to disambiguate how to reduce it
		bool any_odd = false;
		auto oddness = vuple{1,2,3,4,5,6,7,9,11} % 2 == 1; // this is a conjunction
		// any_odd |= oddness; // this won't compile
		any_odd |= oddness.value; // unwrap the conjunction and reduce the vector by the operator
		assert(( any_odd ));

		bool all_odd = false;
		all_odd |= bool(oddness); // reduce the conjunction
		assert(( not all_odd ));

		auto count_odd = 0;
		count_odd += oddness.value;
		assert(( count_odd == 6 ));
	}

	// basic iterators and find
	{
		std::vector a {1,2,3,4,5};
		std::deque b {5,4,3,2,1};
		std::list c {1,1,1,1,1};

		// TODO: libstdc++ goes full clever mode and switches on difference_type here
		// technically it does need to be a "signed integer type" so nothing we can do about it,
		// maybe discourage it by downgrading the iterator category, but that's still rather unfortunate
		// sad sad world...
		// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1522r1.pdf
		// maybe this will allow us to have custom difference type but I have a
		// feeling that it will not be for a random access iterator
		// the funny thing is that the dreaded switch is probably the only hole
		// I can't patch up, there is right shift, comparison with literal, it's
		// all ok, but nah gotta be extra clever, especially in generic code
		// that deals with user defined types, we gotta use a thing that can't
		// ever accept anything user defined... yeah, there is being standard
		// compliant, but then there is also just basic human decency...
		// I hope I'll live to see iterators with range constrained difference
		// types in the standard (like boost safe numerics) and then full of
		// joy come back and enable these examples... or no let's instead have
		// ranges v666 or something
		// assert(( std::find(vbegin(a, b), vend(a, b), vuple{3,3}) == vbegin(a, b) + 2 ));
		// assert(( std::find(vbegin(a, b), vend(a, b), vuple{4,2}) == vbegin(a, b) + 3 ));
		// assert(( std::find_if(vbegin(a, b), vend(a, b), flatten_args(std::equal_to<>{})) == vbegin(a, b) + 2 ));
		// assert(( std::find_if(vbegin(a, b), vend(a, b), flatten_args(std::less<>{})) == vbegin(a, b) ));
		// assert(( std::find_if(vbegin(a, b), vend(a, b), flatten_args(std::greater<>{})) == vbegin(a, b) + 3 ));

		// SOLVED!
		assert(( simple::support::find(vbegin(a, b), vend(a, b), vuple{3,3}) == vbegin(a, b) + 2 ));
		assert(( simple::support::find(vbegin(a, b), vend(a, b), vuple{4,2}) == vbegin(a, b) + 3 ));
		assert(( simple::support::find_if(vbegin(a, b), vend(a, b), flatten_args(std::equal_to<>{})) == vbegin(a, b) + 2 ));
		assert(( simple::support::find_if(vbegin(a, b), vend(a, b), flatten_args(std::less<>{})) == vbegin(a, b) ));
		assert(( simple::support::find_if(vbegin(a, b), vend(a, b), flatten_args(std::greater<>{})) == vbegin(a, b) + 3 ));

		for(auto&& [x,y,z] : simple::support::range{ vbegin(a, b, c), vend(a, b, c) })
		{
			assert(x + y + z == 7);
			x += 10;
			y += 20;
			z += 30;
		}
		assert(( a == std::vector{11,12,13,14,15} ));
		assert(( b == std::deque{25,24,23,22,21} ));
		assert(( c == std::list{31,31,31,31,31} ));

		{
			auto bg = vbegin(a,b,c);
			auto cg = bg++;
			assert(( cg == vbegin(a,b,c) ));
			assert(( bg == vuple{a.begin() + 1,b.begin() + 1,std::next(c.begin())} ));
		}

		assert(( vend(a, b) - vbegin(a, b) == vuple{5,5} ));
		assert(( vend(a, b) - vuple{5,5} == vbegin(a, b) ));
		assert(( vbegin(a, b) + vuple{5,5} == vend(a, b) ));

	}

	// iterators transform
	{
		std::vector a {1,2,3,4,5};
		std::deque b {5,4,3,2,1};
		std::list c {1,1,1,1,1};
		std::array d {0,0,0,0,0};

		// no extra cleverness so far in transform from either libstdc++ or libc++
		std::transform(vbegin(a,b,c), vend(a,b,c), d.begin(), flatten_args([](auto&... xs) { return (xs + ...); }));
		assert(( d == std::array{7,7,7,7,7} ));

		std::transform(vbegin(a,b), vend(a,b), vbegin(c,d), [](const auto& x) { return x + x; });
		assert(( c == std::list{2,4,6,8,10} ));
		assert(( d == std::array{10,8,6,4,2} ));

		std::transform(vbegin(a,b), vend(a,b), vbegin(c,d), flatten_args([](auto& x, auto& y) { return vuple{x+y, x-y}; }));
		assert(( c == std::list{6,6,6,6,6} ));
		assert(( d == std::array{-4,-2,0,2,4} ));

		std::transform(vbegin(a,b), vend(a,b), vbegin(a,b), [](auto&& x) { return x += x; });
		assert(( a == std::vector{2,4,6,8,10} ));
		assert(( b == std::deque{10,8,6,4,2} ));
	}

	// TODO: sort needs relational operators with difference type and scalars,
	// in context of algorithms we're assuming our ranges are of the same size
	// and since all operations are applied uniformly that should be an
	// invariant, so a usual conjunctive comparison of each element should
	// suffice (it's an overkill actually, but gotta stay generic and also
	// assuming simd, however unlikely that is with pointers)

	// TODO: another reason for sort to not work is swap, but it will be fixed in c++23
	{
		using std::swap;

		int a = 1, b = 2;
		int c = 3, d = 4;

		// this works
		auto x = vuple<int&,int&>(a,b);
		auto y = vuple<int&,int&>(c,d);
		swap(x,y);

		// and this will work in c++23
		// swap(vuple<int&,int&>(a,b), vuple<int&,int&>(c,d));

		assert(( a == 3 && b == 4 && c == 1 && d == 2 ));
	}

	// iterators mismatch
	{
		std::array  a{0,1,2,3,4,5,6,7,8,9};
		std::list   b{0,1,2,3,4,5,6,7,8,9};
		std::vector c{0,1,2,3,4,0,6,7};
		std::deque  d{0,1,2,3};

		assert(( vend(a,c,d) - vbegin(a,c,d) >= 0 ));

		assert(( simple::support::mismatch(vbegin(a,b), vend(a,b)) == vend(a,b) ));
		assert(( simple::support::mismatch(vbegin(a,c), vend(a,c)) == vbegin(a,c) + 5 ));
		assert(( simple::support::mismatch(vbegin(c,d), vend(c,d)) == vbegin(c,d) + 4 ));
		assert(( simple::support::mismatch(vbegin(a,b,c,d), vend(a,b,c,d)) ==
			vuple{simple::support::transform([](auto x){return std::next(x,4);},vbegin(a,b,c,d))} ));
			// std::next(vbegin(a,b,c,d),4) )); // i'm asking you to increment this thing 4 times, it's a bidir, libc++, can you freaking increment a bidir 4 times without going all over the place? why are you messing around with the difference type? why do stdlib writers hate generic code so much??
	}

	// shaped iterators
	{
		std::array row1 { 0,1,2,0,4,5, 0,0, 8,9 };
		std::array row2 { 0,1,2,3,4,5, 0,0, 8,9 };

		auto begin = vbegin(row1,row2);
		auto hole = simple::support::find(vuple{begin, begin+1}, vend(row1,row2), 0);
		assert(( hole == vuple{begin+6, begin+7} ));
		assert(( *hole == 0 ));
		// *hole = -1 // nope, type safety
		*hole = *hole - 1; // this is algebra, it's ok
		// *hole -= 1; // TODO: does not work on a technicality... *hole is not an lvalue... it's a tuple of lvalues...
		assert(( *hole == -1 ));
		assert(( row1 == std::array{0,1,2,0,4,5, -1,-1, 8,9} ));
		assert(( row2 == std::array{0,1,2,3,4,5, -1,-1, 8,9} ));
		*hole = *hole + 14;
		assert(( *hole == 13 ));
		assert(( row1 == std::array{0,1,2,0,4,5, 13,13, 8,9} ));
		assert(( row2 == std::array{0,1,2,3,4,5, 13,13, 8,9} ));
		*hole = vuple{
			vuple{21,23},
			vuple{22,24}
		};
		assert(( row1 == std::array{0,1,2,0,4,5, 21,22, 8,9} ));
		assert(( row2 == std::array{0,1,2,3,4,5, 23,24, 8,9} ));
	}

	return 0;
}
